package com.berchen.阻塞与非阻塞;

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

/**
 * 非阻塞式
 * 使用NIO完成网络通信的三个通信：
 *  *  1、通道（Channel）：负责连接。
 *  *      java.nio.channels.Channel接口：
 *  *          |--SelectableChannel    // 网络通道
 *  *              |--SocketChannel
 *  *              |--ServerSocketChannel
 *  *              |--DatagramChannel
 *  *
 *  *              管道
 *  *              |--Pipe.SinkChannel
 *  *              |--Pipe.SourceChannel
 *  *
 *  *  2、缓冲区（Buffer）：负责数据的存取。
 *  *  3、选择器（Selector）：是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况。
 *
 *  将通道切换成非阻塞模式：Channel类中的configureBlocking(false)方法，设置为false;
 *
 *  将通道注册进选择器中：通过通道的register(Selector,int key)方法。
 *      第一个参数就是选择器，第二个参数是选择器要监听的事件类型。
 *      如果注册时不止监听一个事件，则可以使用“位或”（|）操作符连接。
 *  选择器监控的集中状态：
 *  SelectionKey.OP_READ    ==> 对应的key的值  (1)  读
 *  SelectionKey.OP_WRITE   ==> (4)                 写
 *  SelectionKey.OP_CONNECT ==> (8)                 连接
 *  SelectionKey.OP_ACCEPT  ==> (16)                接收
 */
public class TestNonBlock {

    // 客户端
    @Test
    public void client() throws IOException {
        SocketChannel sChannel = SocketChannel.open(
                new InetSocketAddress("127.0.0.1", 10001));

        // 切换成非阻塞模式
        sChannel.configureBlocking(false);
        ByteBuffer buf = ByteBuffer.allocate(1024);

        // 往缓冲区存数据,循环发
        Scanner sc=new Scanner(System.in);
        while(sc.hasNext()){
            String str=sc.next();
            buf.put((LocalDateTime.now().toString()+str).getBytes());
            buf.flip();
            // 从通道写数据
            sChannel.write(buf);
            buf.clear();
        }
        sChannel.close();
    }

    // 服务端
    @Test
    public void serve() throws IOException {

        ServerSocketChannel ssChannel = ServerSocketChannel.open();

        // 切换成非阻塞模式
        ssChannel.configureBlocking(false);

        // 绑定端口号
        ssChannel.bind(new InetSocketAddress(10001));

        // 获取一个选择器
        Selector sele = Selector.open();

        // 将通道注册到选择器中，并且指定监听事件
        ssChannel.register(sele, SelectionKey.OP_ACCEPT);  // 第一个参数就是选择器，第二个参数就是选择器监控的什么状态

        // 轮询式的获取选择器上已经准备就绪的事件。
        while(sele.select()>0){ // 值大于0  就至少有一个准备就绪了

            // 获取当前选择器中所有注册的选择键（已就绪的监听事件）
            Iterator<SelectionKey> it = sele.selectedKeys().iterator();
            while(it.hasNext()){

                SelectionKey s = it.next();
                // 判断具体是什么事件准备就绪
                if(s.isAcceptable()){

                    // 接收就绪,就获取客户端连接
                    try {
                        SocketChannel sChannel = ssChannel.accept();
                        sChannel.configureBlocking(false);  // 切换成非阻塞模式
                        
                        // 将该通道注册到选择器中
                        sChannel.register(sele,SelectionKey.OP_READ);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                else if(s.isReadable()){
                    
                    // 读就绪
                    // 获取当前选择器上读就绪的通道
                    SocketChannel schannel = (SocketChannel) s.channel();

                    // 读取数据
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    int len=0;
                    try {
                        while ((len = schannel.read(buf)) > 0){

                            buf.flip();
                            System.out.println(new String(buf.array(),0,len));
                            buf.clear();
                        }
                    }catch (Exception e){}
                }
            }
            // 取消掉选择键（SelectionKey）
            it.remove();
        }
    }
}
